package com.sunday.common.statemachine.study.e4_Hierarchical;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

/**
 * 选择状态
 * 需要在 state 和 transition to work 中定义 choice 适当地。您可以使用该方法将特定状态标记为选择状态。当转换 配置。choice()
 * <p>
 * 您可以使用 配置过渡，其中定义了源 state 和一个结构，它相当于普通的 .使用 和 ，你可以指定一个守卫 就像您将使用带子句的条件一样。
 * <p>
 * withChoice()
 * first/then/last  =  if/elseif/else
 * first = if
 * then = elseif
 * last = else
 * <p>
 * 过渡需要能够存在，因此您必须确保使用 . 否则，配置格式不正确。以下示例显示了如何定义 a choice 状态：last
 */

@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachine  // 启用Spring状态机功能
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.NEW)
                .choice(OrderStates.CHECKED)
                .state(OrderStates.BACKWASH)
                .state(OrderStates.UNPAID)
                .state(OrderStates.PAID)
                .state(OrderStates.SHIPPED)
                .end(OrderStates.COMPLETED)
                .end(OrderStates.CANCELED)
                .and()  // 结束当前状态定义，开始下一个状态规则

                .withStates()
                .parent(OrderStates.PAID)
                .initial(OrderStates.PENDING_OUTBOUND) // 待出库
                .state(OrderStates.SHIPPING)  // 待发货
                .end(OrderStates.COMPLETED)   // 已完成
        ;

    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                // 新建订单 -> 检查库存 触发条件 检查库存事件
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.NEW)  // 源状态：NEW
                .target(OrderStates.CHECKED)  // 目标状态：CHECKED
                .event(OrderEvents.CHECK_STOCK)  // 触发事件：CHECK_STOCK（检查库存事件）
                .action(orderCreatedAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                // choice 状态分支
                // if(检查库存 == 库存充足) -> 支付状态  else -> 回冲库存
                .withChoice()
                .source(OrderStates.CHECKED)
                .first(OrderStates.UNPAID, inventoryAvailableGuard(), paymentAction()) // 库存充足 -> 支付状态
                .last(OrderStates.BACKWASH, cancelAction()) // 库存不足 -> 取消订单
                .and()

                // 回冲库存 -> 已取消 没有事件触发自动流转
                .withExternal()
                .source(OrderStates.BACKWASH)  // 源状态：BACKWASH
                .target(OrderStates.CANCELED)  // 目标状态：CANCELED
                .action(backwashAction())
                .action(cancelCompleteAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                // 未支付 -> 已支付 触发条件 已支付事件
                .withExternal()
                /**
                 * 状态机层次结构导致的多次事件触发：
                 * 在状态机中定义了层次化状态：
                 * PAID 是一个父状态。
                 * PENDING_OUTBOUND 是 PAID 的子状态。
                 * 当状态从 UNPAID 转换到 PAID 时，实际上触发了两次状态变更：
                 * 从 UNPAID 到 PAID（父状态的变更）。
                 * 自动从 PAID 进入初始子状态 PENDING_OUTBOUND（子状态的变更）。
                 * 因此，你会看到两次状态变更的日志。
                 */
                .source(OrderStates.UNPAID)  // 源状态：UNPAID
                .target(OrderStates.PAID)    // 目标状态：PAID
                .event(OrderEvents.PAY)
                .action(unpaidAction())
                .and()  // 结束当前转换定义，开始下一个转换规则

                // 待出库 -> 待发货 触发条件 出库事件
                .withExternal()
                /*
                 * 已支付(父) -> 待出库(子) 自动切换
                 * .parent(OrderStates.PAID).initial(OrderStates.PENDING_OUTBOUND)
                 * 因为PAID中是子状态，需要用parent(OrderStates.PAID)来指定父状态, 并指定初始子状态, 所以形成了平滑切换
                 */
                // 待出库(子) -> 待发货(子) 触发条件 出库事件
                .source(OrderStates.PENDING_OUTBOUND)   // 源状态：PENDING_OUTBOUND
                .target(OrderStates.SHIPPING)           // 目标状态：SHIPPING
                .event(OrderEvents.OUTBOUND)
                .action(outBoundAction())
                .and()

                // 待发货(子) -> 已发货(父类) 触发条件 发货事件
                .withExternal()
                .source(OrderStates.SHIPPING) // 源状态：SHIPPING
                .target(OrderStates.SHIPPED)  // 目标状态：SHIPPED
                .event(OrderEvents.SHIP)
                .action(shipAction())
                .and()

                .withExternal()
                .source(OrderStates.SHIPPED)  // 源状态：SHIPPED
                .target(OrderStates.COMPLETED)  // 目标状态：COMPLETED
                .event(OrderEvents.COMPLETE)  // 触发事件：COMPLETE（订单完成事件）
                .action(completeAction())
                .and()

                // 新增订单取消的状态转换配置，从不同状态都可以触发取消订单事件，转换到已取消状态
                .withExternal()
                .source(OrderStates.NEW) // 源状态可以是新建订单状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
                .and()
                .withExternal()
                .source(OrderStates.PAID) // 源状态也可以是已支付状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
                .and()
                .withExternal()
                .source(OrderStates.SHIPPED) // 源状态还可以是已发货状态
                .target(OrderStates.CANCELED) // 目标状态为已取消状态
                .event(OrderEvents.CANCEL) // 触发事件为取消订单事件
                .action(cancelCompleteAction())
        ;
    }

    // Guard 判断库存是否充足
    @Bean
    public Guard<OrderStates, OrderEvents> inventoryAvailableGuard() {
        return context -> {
            // 模拟库存判断
//            Boolean stockAvailable = (Boolean) context.getExtendedState().getVariables().getOrDefault("stockAvailable", false);
            Boolean stockAvailable = (Boolean) context.getMessageHeaders().getOrDefault("stockAvailable", false);
            return stockAvailable;
        };
    }

    // 订单创建时的动作
    @Bean
    public Action<OrderStates, OrderEvents> orderCreatedAction() {
        return context -> log.info("订单已创建，开始检查库存...");
    }

    // 支付时的动作
    @Bean
    public Action<OrderStates, OrderEvents> paymentAction() {
        return context -> log.info("库存充足，进入支付流程...");
    }

    // 取消时的动作
    @Bean
    public Action<OrderStates, OrderEvents> cancelAction() {
        return context -> log.info("库存不足，订单已取消...");
    }

    @Bean
    public Action<OrderStates, OrderEvents> outBoundAction() {
        return context -> log.info("已出库，等待发货...");
    }

    @Bean
    public Action<OrderStates, OrderEvents> shipAction() {
        return context -> log.info("已发货，等待完成...");
    }

    // 支付成功后的动作
    @Bean
    public Action<OrderStates, OrderEvents> paymentSuccessAction() {
        return context -> log.info("支付成功，订单已完成！");
    }

    @Bean
    public Action<OrderStates, OrderEvents> cancelCompleteAction() {
        return context -> log.info("订单取消流程完成！");
    }

    @Bean
    public Action<OrderStates, OrderEvents> completeAction() {
        return context -> log.info("订单完成流程完成！");
    }


    // 回冲库存流程完成
    @Bean
    public Action<OrderStates, OrderEvents> backwashAction() {
        return context -> log.info("库存回冲，订单已取消...");
    }

    @Bean
    public Action<OrderStates, OrderEvents> unpaidAction() {
        return context -> log.info("支付中，等待订单结果...");
    }


    /**
     * 为什么触发两次 stateChanged()？
     * <p>
     * 因为状态机中存在层次结构，从 UNPAID 到 PAID 是一次状态变更，而从 PAID 自动切换到 PENDING_OUTBOUND 是另一层次变更。
     * 为什么 UNPAID to PAID 出现在 PENDING_OUTBOUND 后？
     * <p>
     * 日志记录的顺序是由状态进入的顺序决定的：先进入 PAID，再进入其子状态 PENDING_OUTBOUND。但 PENDING_OUTBOUND 是最终的细化状态，因此先记录。
     * 如何解决？
     * <p>
     * 在 stateChanged() 方法中通过 getParent() 方法提取父状态，记录完整的父子状态信息。
     * 也可以考虑使用 entry() 和 exit() 钩子方法进行状态进入和退出日志的更精确记录。
     */
    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State change from {} to {}", from == null ? "无" : from.getId(), to.getId());
            }
        };
    }
}